
import { spawn } from "child_process";
@Injectable()
export class ProjectService extends BaseService {

  getHello(): string {
    return 'Hello World! project';
  }

  async exportCAD(dto: ExportCADDto) {
    console.log(dto);
    // res.data, res.code, res.message
    // 递归查询所有模型，提取ibd图 engine结构
    let engineModel = '_16_5_4_2770141_1385551796225_829110_6619';// 这个未来要怎么
    const sourceProperties = await this.stepManager.javaService.getModelPropertyList(this.projectId, engineModel);

    // 获取engine成员，所有的装配关系放到engeing的ibd中
    if(sourceProperties.length==0){
      throw new ResException(ApiCode.ERROR, '不是CAD生成引擎的模型，无法导出CAD文件。');
    }

    // 至少包含两个图IBD图和BDD图
    const diagramsIDS = sourceProperties.find(x => x.key === 'Namespace-ownedDiagram');

    const CADDiagrams = await this.stepManager.modelService.getModels(dto.projectId, diagramsIDS.value);

    const BDDCADDiagram = CADDiagrams.find(x => x.baseSt === 'SysMLDI::SysMLBlockDefinitionDiagram');
    const IBDCADDiagram = CADDiagrams.find(x => x.baseSt === 'SysMLDI::SysMLInternalBlockDiagram');

    if (CADDiagrams.length < 2 || BDDCADDiagram == null || IBDCADDiagram == null) {
      return { data: '未包含足够的组装信息', code: ApiCode.ERROR, message: '', title: '' };
    }
    const BDDCADDiagramInfo = await this.stepManager.shapeService.getDiagramAllShape(dto.projectId, BDDCADDiagram.id);
    const IBDCADDiagramInfo = await this.stepManager.shapeService.getDiagramAllShape(dto.projectId, IBDCADDiagram.id);
    const IBDCADDiagramElements = IBDCADDiagramInfo.filter(x => x.shapeType === 'Symbol' && x.modelName !== '');
    const blocks = [];
    await Promise.all(IBDCADDiagramElements.map(async(x) => {
      let out = { 'id': x.id, 'modelName': x.modelName, "typeId": x.showAttribute.extraAttribute.typeId, 'typeName': x.showAttribute.extraAttribute.typeName };
      if (out.typeId) {
        let constraintProperty = await this.stepManager.javaService.getModelPropertyListByKeys(this.projectId, out.typeId, ['Namespace-ownedRule']);
        if (constraintProperty && constraintProperty.length > 0 && constraintProperty[0].value.length > 0) {
          // 这里获取的是id,需要进一步获取详细的约束
          out['constrains'] = await this.stepManager.modelService.getModels(dto.projectId, constraintProperty[0].value);
          // showAttribute.extraAttribute.specificationName
        }
      }
      if (out['constrains']) {
        blocks.push(out);
      }
    }));
 

    let blocksCode = ['from cadquery import *'];
    for (let x of blocks) {
      // 只支持圆柱体和长方体
      // box(10, 300, 10)可能直接放到约束中
      // blocksCode.push(`${x.modelName} = Workplane("XY").box(10, 300, 10)`);

      for (let y of (x as any).constrains) {
        // 判断是否有约束
        if ((y as any).name === 'shape') {
          blocksCode.push(`${x.modelName} = ${(y as any).showAttribute.extraAttribute.specificationName}`);
        }

      }

    }

    for (let x of blocks) {
      for (let y of (x as any).constrains) {
        // 判断是否有约束
        if ((y as any).name !== 'shape') {
          blocksCode.push(`${x.modelName}.${(y as any).showAttribute.extraAttribute.specificationName}`);
        }

      }

    }

    // 组装
    blocks.forEach((x, y) => {
      if (y === 0) {
        blocksCode.push(`
assy = (
    Assembly(${x.modelName}, name="${x.modelName}",color=Color("yellow"))`);
      } else {
        // 创建一个包含可能颜色的数组
        const colors = ["brown1", "blueviolet", "darkorchid2", "dodgerblue3"];
        // 随机选择一个颜色
        const randomColor = colors[Math.floor(Math.random() * colors.length)];
        blocksCode.push(`.add(${x.modelName}, name="${x.modelName}",color=Color("${randomColor}")) `);
      }
    });

    // 构建约束

    // showAttribute.extraAttribute.firstEndId
    // data.showAttribute.extraAttribute.propertyPathNames  roleName
    // pathname+roleName
    // metaclass "ConnectorEnd"
    // "SysML::Blocks::NestedConnectorEnd"
    // const allEdgeEnds= IBDCADDiagramInfo.filter(x=>x.shapeKey=='ConnectorEndLabel')

    const allEdgeEnds = IBDCADDiagramInfo.filter(x => (x?.showAttribute.extraAttribute as any).propertyPathNames?.length > 0);
    const allEdges = IBDCADDiagramInfo.filter(x => x.shapeType == 'Edge');
    // 对于每个线 找到 点重合的
    const allPointCoincidence = IBDCADDiagramInfo.filter(x => x.shapeKey == "ConstraintProperty" && x.showAttribute.extraAttribute.typeName === 'PointCoincidence');
    // const allConstrain = IBDCADDiagramInfo.filter(x => x.shapeKey == "ConstraintProperty").map(x => x.showAttribute.extraAttribute.typeName);
    const allConstrain = IBDCADDiagramInfo.filter(x => x.shapeKey == "ConstraintProperty");

    // propertyPathNames为空  可能是连接到了 PointCoincidence上
    // 怎么让3个元素连接成一个约束规则,而且不重复??
    // allEdgeEnds 的每个元素查询属性 端口所述部件 ConnectorEnd-partWithPort
    interface ConstrainMap {
      [id: string]: {constrain?:any, paths:[], constrainType?:string};
    }
    // 对于每个约束 找到对应的双边规则 ,与cadquery constrain 输入匹配，，id为key ,{'id1':{constrain,path1,path2,constrainype},}
    let allConstrainMap = {};
    // getPropertyValuesByIds 批量查询

    // 批量查询edge与约束的关系
    const connectorEndValueMap = await this.stepManager.modelService.getPropertyValuesByIdsV2(dto.projectId, allEdgeEnds.map(x => x.modelId), [PropertyKey['ConnectorEnd_partWithPort']]);

    // y 为连接器的id
    // x 为连接器所连端点的id
    connectorEndValueMap.forEach((x, y) => {
      // [...x.entries()][0][1].value
      const mappedConstrain = IBDCADDiagramInfo.find(it => it.modelId === [...x.entries()][0][1].value);
      // x 为连接器所连端点 ，比如 一个edge,一端连接 PointCoincidence这种 另一端连接 cad 的bottom等
      const pathObj = allEdgeEnds.find(xx => xx.modelId === y).showAttribute.extraAttribute as any;
      const pathString = pathObj.propertyPathNames[0] + '?' + pathObj.roleName;
      console.log(pathString);

      if (mappedConstrain) {
        if (allConstrainMap[mappedConstrain.modelId]) {
          allConstrainMap[mappedConstrain.modelId].paths.push({ id: y, path: pathString });
        } else {
          // allConstrainMap[mappedConstrain.modelId] = { constrain: mappedConstrain, paths: [pathString] };

          allConstrainMap[mappedConstrain.modelId] = { paths: [{ id: y, path: pathString }], typeName: mappedConstrain.showAttribute.extraAttribute.typeName, modelName: mappedConstrain.modelName };

        }

      } else {
        console.log([...x.entries()][0][1].value, '没找到对应的约束');
      }

    });

    // alledge包含 firstEndId 等关系链路 将这些约束联系在一起 也就是3段
    Object.keys(allConstrainMap).forEach(x => {
      // 支持点重合和线平行,面平行约束求解
      if (['LineParallelism', 'PointCoincidence', 'PlaneParallelism', 'PlanePerpendicularity'].includes(allConstrainMap[x].typeName)) {
        // 寻找点重合的双边节点
        let constrainPaths = [];
        allConstrainMap[x].paths.forEach(x1 => {
          let firstRuleID = '';
          allEdges.forEach(x2 => {
            if (x2.showAttribute.extraAttribute.secondEndId === x1.id) {
              firstRuleID = x2.showAttribute.extraAttribute.firstEndId;
            }
            if (x2.showAttribute.extraAttribute.firstEndId === x1.id) {
              firstRuleID = x2.showAttribute.extraAttribute.secondEndId;
            }
          });

          Object.keys(allConstrainMap).forEach(x3 => {
            allConstrainMap[x3].paths.forEach(x4 => {
              if (x4.id === firstRuleID) {
                constrainPaths.push(x4.path);
              }
            });

          });

          // 先定位到一端的id,再根据这个id找到path ，作为rule的输出

        });

        // 生成python constrain代码
        if (constrainPaths.length == 2) {
          if (allConstrainMap[x].typeName === 'PointCoincidence') {
            blocksCode.push(`.constrain("${constrainPaths[0]}", "${constrainPaths[1]}", "Point")`);
          }

          if (allConstrainMap[x].typeName === 'PlaneParallelism') {
            blocksCode.push(`.constrain("${constrainPaths[0]}", "${constrainPaths[1]}", "Plane")`);
          }

          if (allConstrainMap[x].typeName === 'LineParallelism') {
            blocksCode.push(`.constrain("${constrainPaths[0]}", "${constrainPaths[1]}", "Axis", param=0)`);
          }
          if (allConstrainMap[x].typeName === 'PlanePerpendicularity') {
            blocksCode.push(`.constrain("${constrainPaths[0]}", "${constrainPaths[1]}", "Plane", param=90)`);
          }
        }

      }
    });

    blocksCode.push(
      `.solve()    
)
`);
    blocksCode.push(`assy.save(r"${dto.outFileURL}", "STEP", mode="fused")`);
    let pythonCode = blocksCode.join('\n');
    console.log('pythonCode', pythonCode);
    // 从python模板生成
    let pythonCodeTest = `
from cadquery import *


output = Workplane("XY").box(10, 300, 10)
output.faces(">X and #Z").tag("right")
output.faces("<X and #Z").tag("left")
output.faces(">Z").tag("top")

output.vertices(">(10,270,5)").tag("mountPoint1")
# Define the parts
leftCrank = Workplane("XY").circle(5).extrude(50)

rightCrank = Workplane("XY").circle(5).extrude( 50)
rightCrank.faces("<Z").tag("bottom")
rightCrank.faces(">Z").tag("top")
rightCrank.faces(">Z").edges().tag("topNormal")

leftRod = Workplane("XY").circle(5).extrude(50)
leftPiston = Workplane("XY").circle(20).extrude(40)
rightRod = Workplane("XY").circle(5).extrude(50)
rightPiston = Workplane("XY").circle(20).extrude(40)
#part2.faces("<X").edges("<Z").vertices("<Y").tag("pt2")
#and not or 
#edges("not(<X or >X or <Y or >Y)")
#edges("|Z and >Y")
#.constrain("part1?pt2", "part2?pt2", "Point")


leftCrank.faces("<Z").tag("bottom")
leftCrank.faces(">Z").tag("top")
leftCrank.faces(">Z").edges().tag("topNormal")


rightRod.faces(">Z").tag("top")
rightRod.faces("<Z").tag("bottom")

leftRod.faces(">Z").tag("top")
leftRod.faces("<Z").tag("bottom")

leftPiston.faces("<Z").tag("bottom")
leftPiston.faces(">Z").tag("topNormal")

rightPiston.faces("<Z").tag("bottom")
rightPiston.faces(">Z").tag("topNormal")
pistonVec = Vector(0, 1, 0)

#output.tag("topNormal")

assy = (
      Assembly(output, name="output",color=Color("yellow"))    
    .add(rightCrank, name="rightCrank",color=Color("green"))      
    .add(leftCrank, name="leftCrank",color=Color("red"))  
    .add(leftRod, name="leftRod",color=Color("red"))  
    .add(rightRod, name="rightRod",color=Color("green"))  
    
    .add(rightPiston, name="rightPiston",color=Color("red"))  
    .add(leftPiston, name="leftPiston",color=Color("green"))  
    

    .constrain("output?right", "rightCrank?bottom", "Plane")
    .constrain("output?left", "leftCrank?bottom", "Plane") #tag1
    .constrain("output?mountPoint1", "rightCrank?bottom", "Point")

    .constrain("leftCrank?top", "leftRod?bottom", "Point")
    .constrain("rightCrank?top", "rightRod?bottom", "Point")

    
    .constrain("rightRod?top", "rightPiston?bottom", "Point")
    .constrain("rightRod?bottom", "rightCrank?top", "Point")    
    .constrain("leftRod?top", "leftPiston?bottom", "Point")
    .constrain("leftRod?bottom", "leftCrank?top", "Point")


#constraints.append(cq.LineDirection(rightPiston.topNormal, pistonVec))
#constraints.append(cq.LineDirection(leftPiston.topNormal, pistonVec))
    .constrain("rightPiston?topNormal", "output?top", "Axis", param=0)
    .constrain("leftPiston?topNormal", "output?top", "Axis", param=0)    
#  .constrain("leftRod?bottom", "rightRod?bottom", "Axis", param=90)
    
    
    .solve()
)
#show_object(assy)
#assy.save("out.stp", "STEP", mode="fused")

# Specify additional options such as glue as keyword arguments

assy.save(r"${dto.outFileURL}", "STEP", mode="fused")
`;

    await this.runPythonCode(pythonCode);
 

    return { data: 'export', code: 1000, message: '', title: '' };
  }
  async runPythonCode(pythonCode) {

    // const { spawn } = require('child_process');

    // 创建 Python 子进程
    const python = spawn('python', ['-']); // 注意这里的 '-'，它告诉 Python 从 stdin 读取数据

    // 将 Python 代码写入子进程的 stdin
    python.stdin.write(pythonCode);
    python.stdin.end(); // 表示没有更多的数据要写入

    // 处理子进程的输出
    python.stdout.on('data', (data) => {
      console.log(`stdout: ${data}`);
    });

    python.stderr.on('data', (data) => {
      console.log(`stderr: ${data}`);
      throw new ResException(ApiCode.ERROR, `python code error: ${data}`);
    });

    python.on('close', (code) => {
      console.log(`child process exited with code ${code}`);
    });
  }


}